.. _глава_72_start:


Указатели на функции, делегаты и лямбда-выражения
=================================================

.. index::
    lambda
    лямбда
    делегат
    delegate
    function
    функция

Указатели на функции предназначены для хранения адресов функций для последующего выполнения этих
функций. Указатели на функции аналогичны своим аналогам в
языке программирования Си.
Делегаты хранят как указатель на функцию, так и контекст, в котором этот указатель на функцию выполняется. Сохраненный контекст может быть либо областью, в которой
будет выполняться функция, либо объектом структуры или класса.
Делегаты также позволяют закрывать, концепция, которая поддерживается большинством функциональных
языков программирования.

Указатели на функции
---------------------

В предыдущей главе мы видели, что с помощью оператора & можно брать адреса функций. В одном из примеров мы передали такой
адрес шаблону функции.
Воспользовавшись тем, что параметры типа шаблона могут соответствовать любому типу, давайте передадим указатель функции шаблону, чтобы узнать ее тип, распечатав свойство
свойство .stringof:

.. code-block:: d

    import std.stdio;

    int myFunction(char c, double d)
    {
        return 42;
    }

    void main()
    {
        myTemplate(&myFunction); // Taking the function's address and
        // passing it as a parameter
    }

    void myTemplate(T)(T parameter)
    {
        writeln("type : ", T.stringof);
        writeln("value: ", parameter);
    }

Вывод программы показывает тип и адрес функции myFunction()

.. code-block:: console

    type : int function(char c, double d)
    value: 406948

**Указатели на функции-члены**

Адрес функции-члена может быть взят либо на тип, либо на объект типа, с разными результатами:

.. code-block:: d

     struct MyStruct
     {
         void func()
         {
         }
     }

     void main()
     {
         auto o = MyStruct();
         auto f = &MyStruct.func; // on a type
         auto d = &o.func;        // on an object
         static assert(is(typeof(f) == void function()));
         static assert(is(typeof(d) == void delegate()));
     }

Как показывают два static assert выше, f - это функция, а d - делегат.
делегат. Ниже мы увидим, что d можно вызывать напрямую, а для f нужен объект
объект для вызова.

**Определение**

Подобно обычным указателям, каждый тип указателя функции может указывать точно на
определенный тип функции; список параметров и тип возврата указателя функции и функции должны совпадать.
указателя и функции должны совпадать. Функциональные указатели определяются с помощью
ключевое слово function между типом возврата и списком параметров данного типа.
конкретного типа:

.. code-block:: d

    return_type function(parameters) ptr;

Имена параметров (c и d в выводе выше) необязательны. Поскольку
myFunction() принимает char и double и возвращает int, тип указателя функции, который может указывать на myFunction(), должен быть определен соответствующим образом.
указателя функции, который может указывать на myFunction(), должен быть определен соответствующим образом:

.. code-block:: d

    int function(char, double) ptr = &myFunction;

Строка выше определяет ptr как функцию-указатель, принимающую два параметра (char и
double) и возвращающую int. Его значение - адрес функции myFunction().
Синтаксис функциональных указателей относительно сложнее для чтения; обычно для того, чтобы сделать код
более удобным для чтения с помощью псевдонима:

.. code-block:: d

    alias CalculationFunc = int function(char, double);

Этот псевдоним облегчает чтение кода:

.. code-block:: d

    CalculationFunc ptr = &myFunction;

Как и в случае с любым типом, можно использовать и авто:

.. code-block:: d

    auto ptr = &myFunction;

**Вызов указателя функции**

Указатели функций можно вызывать точно так же, как и функции:

.. code-block:: d

    int result = ptr('a', 5.67);
    assert(result == 42);

Вызов ptr('a', 5.67) выше эквивалентен вызову настоящей функции по адресу
myFunction('a', 5.67).

**Когда использовать**

Поскольку указатели функций хранят информацию о том, какую функцию нужно вызвать, и вызываются они точно так же.
как и функции, на которые они указывают, указатели функций эффективно сохраняют
поведение программы для последующего использования.
В D есть много других возможностей, связанных с поведением программы. Например,
например, подходящая функция для вычисления зарплаты сотрудника
может быть определена по значению члена перечисления:

.. code-block:: d

     final switch (employee.type)
     {
     case EmployeeType.fullTime : fullTimeEmployeeWages();
         break;
     case EmployeeType.hourly : hourlyEmployeeWages();
         break;
     }

К сожалению, этот метод относительно сложнее поддерживать, потому что он явно
должен поддерживать все известные типы сотрудников. Если в программу добавляется новый тип сотрудника
в программу, то все подобные операторы switch должны быть расположены таким образом, чтобы для нового типа сотрудника добавлялось новое выражение case
для нового типа сотрудников.
Более распространенной альтернативой реализации различий в поведении является
полиморфизм. Может быть определен интерфейс Employee, и разные варианты расчета зарплаты
и различные вычисления заработной платы могут обрабатываться различными реализациями этого интерфейса:

.. code-block:: d

    interface Employee
    {
        double wages();
    }

    class FullTimeEmployee : Employee
    {
        double wages()
        {
            double result;
            // ...
            return result;
        }
    }

    class HourlyEmployee : Employee
    {
        double wages()
        {
            double result;
            // ...
            return result;
        }
    }
    // ...
    double result = employee.wages();

Указатели функций - это еще одна альтернатива для реализации различных
поведения. Они чаще всего встречаются в языках программирования, которые не поддерживают
объектно-ориентированное программирование.

**Указатель функции в качестве параметра**

Давайте разработаем функцию, которая принимает массив и возвращает другой массив. Эта
функция будет отфильтровывать элементы со значениями меньше или равными нулю и
умножать остальные на десять:

.. code-block:: d

    int[] filterAndConvert(const int[] numbers)
    {
        int[] result;
        foreach (e; numbers)
        {
            if (e > 0)
            { // filtering,
                immutable newNumber = e * 10; // and conversion
                result ~= newNumber;
            }
        }
        return result;
    }

Следующая программа демонстрирует его поведение со случайно сгенерированными
значениями:

.. code-block:: d

    import std.stdio;
    import std.random;

    void main()
    {
        int[] numbers;
        // Random numbers
        foreach (i; 0 .. 10)
        {
            numbers ~= uniform(0, 10) - 5;
        }
        writeln("input : ", numbers);
        writeln("output: ", filterAndConvert(numbers));
    }

На выходе получаются числа, в десять раз превышающие исходные, которые
изначально были больше нуля. Исходные числа, которые были
выделены:

.. code-block:: console

    input : [-2, 2, -2, 3, -2, 2, -1, -4, 0, 0]
    output: [20, 30, 20

Функция filterAndConvert() предназначена для выполнения очень специфической задачи: она всегда выбирает числа, которые
больше нуля и всегда умножает их на десять. Она могла бы быть более полезной
если бы поведение фильтрации и преобразования было параметризовано.
Отметим, что фильтрация - это тоже форма преобразования (из int в bool),
filterAndConvert() выполняет два преобразования:
∙number > 0, в результате которого получается bool, если рассматривать значение int.
∙number * 10, в результате чего из значения int получается int.
Давайте определим удобные псевдонимы для указателей функций, которые будут соответствовать двум
приведенным выше преобразованиям:

.. code-block:: d

    alias Predicate = bool function(int); // makes bool from int
    alias Convertor = int function(int); // makes int from int

Predicate - это тип функций, которые принимают int и возвращают bool, а Convertor
тип функций, которые принимают int и возвращают int.
Если мы предоставим указатели таких функций в качестве параметров, мы можем заставить
filterAndConvert() использовать эти указатели функций во время своей работы:

.. code-block:: d

    int[] filterAndConvert(const int[] numbers,
    Predicate predicate,
    Convertor convertor)
    {
        int[] result;
        foreach (number; numbers)
        {
            if (predicate(number))
            {
                immutable newNumber = convertor(number);
                result ~= newNumber;
            }
        }
        return result;
    }

filterAndConvert() теперь является алгоритмом, который не зависит от фактических
операций фильтрации и преобразования. При желании его прежнее поведение может быть
можно добиться с помощью следующих двух простых функций:

.. code-block:: d

     bool isGreaterThanZero(int number)
     {
         return number > 0;
     }

     int tenTimes(int number)
     {
         return number * 10;
     }
     // ...
     writeln("output: ", filterAndConvert(numbers,
             Function Pointers, Delegates, and Lambdas
             & isGreaterThanZero, & tenTimes));

Такая конструкция позволяет вызывать filterAndConvert() с любыми способами фильтрации и
преобразования. Например, следующие две функции заставят
filterAndConvert() производить отрицания четных чисел:

.. code-block:: d

     bool isEven(int number)
     {
         return (number % 2) == 0;
     }

     int negativeOf(int number)
     {
         return -number;
     }
     // ...
     writeln("output: ", filterAndConvert(numbers, & isEven,  & negativeOf))

Вывод:

.. code-block:: d

     input : [3, -3, 2, 1, -5, 1, 2, 3, 4, -4]
     output: [-2, -2, -4, 4]

Как видно из этих примеров, иногда такие функции настолько тривиальны, что их определение
их как собственные функции с именем, типом возврата, списком параметров и фигурными
скобками является излишне многословным.
Как мы увидим ниже, => синтаксис анонимных функций делает код
более кратким и читабельным. В следующей строке есть анонимные функции
которые являются эквивалентами isEven() и negativeOf(), без соответствующих определений функций
определения:


.. code-block:: d

    writeln("output: ", filterAndConvert(numbers,
    number => (number % 2) == 0,
    number => -number));

**Функциональный указатель как член**

Указатели функций могут храниться как члены структур и классов. Чтобы увидеть
в этом, давайте спроектируем класс, который принимает предикат и конвертор в качестве параметров конструктора
чтобы использовать их в дальнейшем:

.. code-block:: d

    class NumberHandler
    {
        Predicate predicate;
        Convertor convertor;
        this(Predicate predicate, Convertor convertor)
        {
            this.predicate = predicate;
            this.convertor = convertor;
        }

        int[] handle(const int[] numbers)
        {
            int[] result;
            foreach (number; numbers)
            {
                if (predicate(number))
                {
                    immutable newNumber = convertor(number);
                    result ~= newNumber;
                }
            }
            return result;
        }
    }

Объект такого типа можно использовать аналогично filterAndConvert():

.. code-block:: d

    auto handler = new NumberHandler(&isEven, &negativeOf);
    writeln("result: ", handler.handle(numbers));

Анонимные функции
------------------

.. index::
    анонимная функция
    anonymous function

Код может быть более читабельным и лаконичным, если определять короткие функции
без соответствующих определений функций.
Анонимные функции, которые также известны как литералы функций или лямбды,
позволяют определять функции внутри выражений. Анонимные функции можно использовать
в любой точке, где можно использовать указатель функции.
К их более краткому => синтаксису мы перейдем ниже. Сначала посмотрим их полный синтаксис,
который обычно слишком многословен, особенно когда он появляется внутри других выражений:

.. code-block:: d

    function return_type(parameters) { /* operations */ }

Например, объект NumberHandler, который выдает 7 чисел, которые
больше 2, может быть построен с помощью анонимных функций, как показано в следующем примере
код:

.. code-block:: d

    new NumberHandler(function bool(int number) { return number > 2; },
    function int(int number) { return number * 7; });

Два преимущества приведенного выше кода в том, что функции не определены как собственные
и что их реализации видны прямо там, где конструируется объект NumberHandler.
Обратите внимание, что синтаксис анонимных функций очень похож на синтаксис обычных функций
синтаксис. Хотя такая согласованность имеет свои преимущества, полный синтаксис анонимных
функций делает код слишком многословным.
По этой причине существуют различные более короткие способы определения анонимных
функций.

**Более короткий синтаксис**

Если тип возврата может быть выведен из оператора возврата внутри анонимной функции, то тип возврата указывать не нужно (Место, где
где обычно указывается тип возврата, выделено комментариями к коду).


.. code-block:: d

    new NumberHandler(function /**/(int number) { return number > 2; },
    function /**/(int number) { return number * 7; });

Кроме того, если анонимная функция не принимает параметров, ее список параметров
не нужно указывать. Рассмотрим функцию, принимающую указатель функции
которая ничего не принимает и возвращает double:

.. code-block:: d

    void foo(double function() func) {
    // ...
    }

Анонимные функции, передаваемые этой функции, не должны иметь пустой
список параметров. Поэтому все три следующих синтаксиса анонимных функций
эквивалентны:

.. code-block:: d

    foo(function double() { return 42.42; });
    foo(function () { return 42.42; });
    foo(function { return 42.42; });

Первая написана в полном синтаксисе. Во втором опускается возвращаемый тип,
используя преимущества вычитания типов возврата. В третьем опускается
ненужный список параметров.
Более того, ключевое слово function также не обязательно указывать. В этом случае
компилятору остается определить, является ли это анонимной функцией или
анонимный делегат. Если она не использует переменную из одной из объемлющих областей, она
является функцией:

.. code-block:: d

    foo({ return 42.42; });

Большинство анонимных функций можно определить еще короче с помощью синтаксиса лямбда.

**Синтаксис лямбды вместо одного оператора возврата**

В большинстве случаев даже самый короткий синтаксис, приведенный выше, излишне загроможден. Фигурные
скобки, которые находятся внутри списка параметров функции, делают код более трудным для чтения
читать, а оператор return и точка с запятой внутри аргумента функции
выглядит неуместно.
Давайте начнем с полного синтаксиса анонимной функции, которая имеет единственный
оператор возврата:


.. code-block:: d

    function return_type(parameters) { return expression; }

Мы уже видели, что ключевое слово function не обязательно, а возвращаемый
тип можно вывести:

.. code-block:: d

    (parameters) { return expression; }

Эквивалентом этого определения является следующий синтаксис =>, где символы =>
заменяют фигурные скобки, ключевое слово return и точку с запятой:

.. code-block:: d

    (parameters) => expression

Смысл этого синтаксиса можно сформулировать так: "Учитывая эти параметры,
вычислить это выражение (значение)".
Кроме того, когда имеется один параметр, круглые скобки вокруг
можно опустить:

.. code-block:: d

    single_parameter => expression

С другой стороны, чтобы избежать грамматической двусмысленности, список параметров все равно должен быть
в виде пустых круглых скобок, если в нем нет ни одного параметра:

.. code-block:: d

    () => expression

Программисты, знакомые с lambdas из других языков, могут ошибиться и
использовать фигурные скобки после символов =>, что может быть правильным синтаксисом D с
другим значением:

.. code-block:: d


    // A lambda that returns 'a + 1'
    auto l0 = (int a) => a + 1
    // A lambda that returns a parameter-less lambda that
    // returns 'a + 1'
    auto l1 = (int a) => { return a + 1; }
    assert(l0(42) == 43);
    assert(l1(42)() == 43); // Executing what l1 return

Давайте используем синтаксис лямбда в предикате, передаваемом в std.algorithm.filter.
filter() принимает предикат в качестве параметра шаблона и диапазон в качестве параметра функции
параметр. Она применяет предикат к каждому элементу диапазона и возвращает те
те, которые удовлетворяют предикату. Одним из нескольких способов указания предиката является
синтаксис лямбды.
(Примечание: Мы рассмотрим диапазоны в одной из следующих глав. На данный момент достаточно знать, что
знать, что срезы D - это диапазоны.)
Следующая лямбда - это предикат, который соответствует элементам, которые больше
чем 10:

.. code-block:: d

    import std.stdio;
    import std.algorithm;

    void main()
    {
        int[] numbers = [20, 1, 10, 300, -2];
        writeln(numbers.filter!(number => number > 10));
    }

Вывод содержит элементы, удовлетворяющие предикату:

.. code-block:: console

    [20, 300]

Для сравнения напишем ту же лямбду в самом длинном синтаксисе. Фигурные
скобки, определяющие тело анонимной функции, выделены:

.. code-block:: d

    writeln(numbers.filter!(function bool(int number) {
        return number > 10;
    }));

В качестве другого примера, на этот раз определим анонимную функцию, которая принимает два
параметра. Следующий алгоритм берет два фрагмента и передает их
соответствующие элементы по одному в функцию, которая сама принимает два
параметра. Затем он собирает и возвращает результаты в виде другого фрагмента:

.. code-block:: d

     import std.exception;

     int[] binaryAlgorithm(int function(int, int) func,
         const int[] slice1,
         const int[] slice2)
     {
         enforce(slice1.length == slice2.length);
         int[] results;
         foreach (i; 0 .. slice1.length)
         {
             results ~= func(slice1[i], slice2[i]);
         }
         return results;
     }


Поскольку вышеуказанная функция принимает два параметра, лямбды, которые могут быть переданы
которые можно передать в binaryAlgorithm(), также должны принимать два параметра:

.. code-block:: d

     import std.stdio;

     void main()
     {
         writeln(binaryAlgorithm((a, b) => (a * 10) + b,
                 [1, 2, 3],
                 [4, 5, 6]));
     }

Выход содержит десятикратное количество элементов первого массива плюс элементы
второго массива (например, 14 - это 10 * 1 + 4):

.. code-block:: d

    [14,25,36]

Делегаты
---------

Делегат - это комбинация указателя функции и контекста, в котором она должна быть
в котором она должна быть выполнена. Делегаты также поддерживают замыкания в D. Замыкания - это функция, поддерживаемая
многими функциональными языками программирования.
Как мы уже видели в главе "Время жизни и основные операции" (:ref:`Время жизни и фундаментальные операции<глава_44_start>`), время жизни переменной заканчивается при выходе из области видимости, в которой она определена:

.. code-block:: d

    {
      int increment = 10;
      // ...
    } // ← the life of 'increment' ends here

Поэтому адрес такой локальной переменной не может быть возвращен из функции
функции.
Представим, что increment - это локальная переменная функции, которая сама возвращает
функцию. Сделаем так, чтобы возвращаемая лямбда использовала эту локальную переменную
переменную:

.. code-block:: d

    alias Calculator = int function(int);
    Calculator makeCalculator()
    {
        int increment = 10;
        return value => increment + value; // ← ошибка компиляции
    }

Этот код ошибочен, потому что возвращаемая лямбда использует локальную переменную
которая вот-вот выйдет из области видимости. Если бы код был разрешен к компиляции, лямбда
пыталась бы получить доступ к инкременту, жизнь которого уже закончилась.
Чтобы этот код был скомпилирован и работал корректно, время жизни инкремента
должно быть по крайней мере таким же, как и время жизни лямбды, которая его использует. Делегаты увеличивают
время жизни контекста лямбды, чтобы локальное состояние, которое использует функция.
остается в силе.
Синтаксис делегатов похож на синтаксис функций, единственное отличие - ключевое слово
ключевое слово. Этого изменения достаточно, чтобы предыдущий код компилировался

.. code-block:: d

    alias Calculator = int delegate(int);
    Calculator makeCalculator()
    {
        int increment = 10;
        return value => increment + value;
    }

После использования делегатом приращение локальной переменной будет жить до тех пор.
пока жив этот делегат. Переменная доступна делегату так же, как и любая
любая другая переменная, изменяемая по мере необходимости. Мы увидим примеры этого в
в следующей главе при использовании делегатов с функциями-членами opApply().
Ниже приведен тест делегата, описанного выше:

.. code-block:: d

    auto calculator = makeCalculator();
    writeln("The result of the calculation: ", calculator(3));

Обратите внимание, что makeCalculator() возвращает анонимный делегат. Код выше
присваивает этот делегат переменной calculator, а затем вызывает его по команде
calculator(3). Поскольку делегат реализован так, чтобы возвращать сумму своего
и переменной increment, код выводит сумму 3 и 10

.. code-block:: console

    The result of the calculation: 13

Более короткий синтаксис
Как мы уже использовали в предыдущем примере, делегаты могут воспользоваться преимуществами
более коротким синтаксисом. Когда не указана ни функция, ни делегат,
тип лямбды определяется компилятором в зависимости от того, обращается ли эта лямбда к локальному состоянию.
лямбда обращается к локальному состоянию. Если да, то это делегат.
В следующем примере есть делегат, который не принимает никаких параметров:

.. code-block:: d

    int[] delimitedNumbers(int count, int delegate() numberGenerator)
    {
        int[] result = [-1];
        result.reserve(count + 2);
        foreach (i; 0 .. count)
        {
            result ~= numberGenerator();
        }
        result ~= -1;
        return result;
    }

Функция delimitedNumbers() генерирует фрагмент, в котором первый и последний
элементы равны -1. Она принимает два параметра, задающих остальные элементы, которые находятся
между этими первым и последним элементами.
Давайте вызовем эту функцию с помощью тривиального делегата, который всегда возвращает одно и то же
значение. Помните, что при отсутствии параметров список параметров лямбды
должен быть указан пустым:

Вывод:

.. code-block:: console

    -1 42 42 42 -1

Давайте вызовем delimitedNumbers() на этот раз с делегатом, который использует локальную
переменную:

.. code-block:: d

    int lastNumber;
    writeln(delimitedNumbers(
            15, () => lastNumber += uniform(0, 3)));
    writeln("Last number: ", lastNumber);


Хотя этот делегат выдает случайное значение, поскольку оно добавляется к
предыдущему, ни одно из сгенерированных значений не будет меньше своего предшественника:

.. code-block:: console

    -1 0 2 3 4 6 6 8 9 9 9 10 12 14 15 17 -1
    Последнее число: 17

**Объект и функция-член в качестве делегата**

Мы видели, что делегат - это не что иное, как указатель функции и контекст.
в котором она должна быть выполнена. Вместо этих двух элементов делегат также может состоять
из функции-члена и существующего объекта, для которого эта функция-член должна быть вызвана
вызывается.
Синтаксис, определяющий такой делегат от объекта, следующий:

.. code-block:: d

    &object.member_function

Сначала заметим, что такой синтаксис действительно определяет делегат, выводя его
строковое представление:


.. code-block:: d

    import std.stdio;

    struct Location
    {
        long x;
        long y;
        void moveHorizontally(long step)
        {
            x += step;
        }

        void moveVertically(long step)
        {
            y += step;
        }
    }

    void main()
    {
        auto location = Location();
        writeln(typeof(&location.moveHorizontally).stringof);
    }

Согласно выводу, тип moveHorizontally(), вызываемого в location, является
действительно делегатом:

.. code-block:: console

    void delegate(long step)

Обратите внимание, что синтаксис & используется только для создания делегата. Делегат будет
позже с помощью синтаксиса вызова функции:

.. code-block:: d

    // The definition of the delegate variable:
    auto directionFunction = &location.moveHorizontally;
    // Calling the delegate by the function call syntax:
    directionFunction(3);
    writeln(location);

Поскольку делегат объединяет объект location и функцию-член moveHorizontally()
вызов делегата эквивалентен вызову функции
moveHorizontally() для location. Вывод показывает, что объект
действительно переместился на 3 шага по горизонтали:

.. code-block:: console

    Location(3, 0)

Указатели функций, лямбды и делегаты - это выражения. Они могут использоваться в
там, где ожидается значение их типа. Например, фрагмент делегата
инициализируется ниже из делегатов, построенных из объекта и его
различных функций-членов. Элементы делегата в срезе впоследствии вызываются
как и функции:

.. code-block:: d

    auto location = Location();
    void delegate(long)[] movements =
        [
            &location.moveHorizontally,
            &location.moveVertically,
            &location.moveHorizontally
        ];
    foreach (movement; movements)
    {
        movement(1);
    }
    writeln(location);

В соответствии с элементами среза, расположение было изменено дважды
по горизонтали и один раз по вертикали:

.. code-block:: d

    Location(2, 1)

**Свойства делегата**

Доступ к указателям функции и контекста делегата можно получить через его
.funcptr и .ptr, соответственно:

.. code-block:: d

    struct MyStruct
    {
        void func()
        {
        }
    }

    void main()
    {
        auto o = MyStruct();
        auto d = &o.func;
        assert(d.funcptr == &MyStruct.func);
        assert(d.ptr == &o);
    }

Можно создать делегат с нуля, задав эти свойства
явно:

.. code-block:: d

    struct MyStruct
    {
        int i;
        void func()
        {
            import std.stdio;

            writeln(i);
        }
    }

    void main()
    {
        auto o = MyStruct(42);
        void delegate() d;
        assert(d is null); // null to begin with
        d.funcptr = &MyStruct.func;
        d.ptr = &o;
        d();
    }

Вызов делегата выше как d() эквивалентен выражению o.func() (т.е.
вызов MyStruct.func на o):

.. code-block:: console

    42

**Ленивые параметры - это делегаты**

Мы познакомились с ключевым словом lazy в главе "Параметры функций" (:ref:`Параметры функций<глава_35_start>`):


.. code-block:: d

    void log(Level level, lazy string message)
    {
        if (level >= interestedLevel)
        {
            writeln(message);
        }
    }
    // ...
    if (failedToConnect)
    {
        log(Level.medium,
            format("Failure. The connection state is '%s'.",
                getConnectionState()));
    }

Поскольку message - это ленивый параметр, все выражение формата
(включая вызов getConnectionState(), который оно делает) будет оценено, если
и когда этот параметр используется внутри log().

За кулисами ленивые параметры на самом деле являются делегатами, а аргументы, которые
передаваемые ленивым параметрам, являются объектами делегатов, которые создаются автоматически
компилятором. Приведенный ниже код эквивалентен приведенному выше:

.. code-block:: d

    void log(Level level, string delegate() lazyMessage)
    { // (1)
        if (level >= interestedLevel)
        {
            writefln("%s", lazyMessage()); // (2)
        }
    }
    // ...
    if (failedToConnect)
    {
        log(Level.medium,
            delegate string()
        { // (3)
            return format(
                "Failure. The connection state is '%s'.",
                getConnectionState());});
        }

#. Параметр lazy - это не строка, а делегат, возвращающий строку.
#. Делегат вызывается для получения возвращаемого значения.
#. Все выражение заворачивается внутрь делегата и возвращается из него

**Ленивые переменные функции**

Когда функции требуется переменное число ленивых параметров, обязательно
невозможно указать это неизвестное число параметров как ленивые.
Решением является использование переменных параметров делегата. Такие параметры могут
принимать любое количество выражений, которые совпадают с типом возврата этих
делегатов. Делегаты не могут принимать параметры:

.. code-block:: d

    import std.stdio;

    void foo(double delegate()[] args...)
    {
        foreach (arg; args)
        {
            writeln(arg()); // Calling each delegate
        }
    }

    void main()
    {
        foo(1.5, () => 2.5); // 'double' passed as delegate
    }

Обратите внимание, что переменному параметру сопоставляются как двойное выражение, так и лямбда
параметру. Двойное выражение автоматически заворачивается внутрь делегата
и функция выводит значения всех своих эффективно зацикленных параметров:

.. code-block:: console

    1.5
    2.5

Ограничение этого метода заключается в том, что все параметры должны быть одного типа (double
выше). Позже, в главе "Больше шаблонов" (:ref:`Дополнительная информация о шаблонах<глава_78_start>`), мы увидим, как использовать преимущества
воспользоваться преимуществами параметров шаблона кортежа, чтобы снять это ограничение].


toString() с параметром делегата
--------------------------------

До этого момента мы определили множество функций toString() для
представления объектов в виде строк. Все эти определения toString() возвращали строку
не принимая никаких параметров. Как отмечено в строках комментариев ниже, структуры и
классы воспользовались функциями toString() своих соответствующих членов, просто передавая эти члены в format().

.. code-block:: d

    import std.stdio;
    import std.string;

    struct Point
    {
        int x;
        int y;
        string toString() const
        {
            return format("(%s,%s)", x, y);
        }
    }

    struct Color
    {
        ubyte r;
        ubyte g;
        ubyte b;
        string toString() const
        {
            return format("RGB:%s,%s,%s", r, g, b);
        }
    }

    struct ColoredPoint
    {
        Color color;
        Point point;
        string toString() const
        {
            /* Taking advantage of Color.toString and
    * Point.toString: */
            return format("{%s;%s}", color, point);
        }
    }

    struct Polygon
    {
        ColoredPoint[] points;
        string toString() const
        {
            /* Taking advantage of ColoredPoint.toString: */
            return format("%s", points);
        }
    }

    void main()
    {
        auto polygon = Polygon(
            [
            ColoredPoint(Color(10, 10, 10), Point(1, 1)),
            ColoredPoint(Color(20, 20, 20), Point(2, 2)),
            ColoredPoint(Color(30, 30, 30), Point(3, 3))
        ]);
        writeln(polygon);
    }

Для того чтобы полигон был отправлен на вывод в виде строки в последней строке
программы, все функции toString() Polygon, ColoredPoint, Color и
Point вызываются косвенно, создавая при этом в общей сложности 10 строк. Обратите внимание, что
строки, созданные и возвращенные функциями нижнего уровня, используются
используются только один раз соответствующей функцией более высокого уровня, которая их вызвала.
Однако, хотя в общей сложности создается 10 строк, только последняя из них
выводится на экран:

.. code-block:: console

    [{RGB:10,10,10;(1,1)}, {RGB:20,20,20;(2,2)}, {RGB:30,30,30;(3,3)}]


Несмотря на практичность, этот метод может ухудшить производительность программы
из-за большого количества строковых объектов, которые создаются и тут же
выбрасываются.
Перегрузка toString() позволяет избежать этой проблемы, поскольку принимает
параметр делегата:

.. code-block:: d

    void toString(void delegate(const(char)[]) sink) const

Как видно из объявления, эта перегрузка toString() не возвращает строку.
Вместо этого символы, которые будут напечатаны, передаются ее делегату
параметр. Делегат отвечает за добавление этих символов к
единственной строке, которая будет выведена на экран.
Все, что нужно сделать программисту, - это вызвать
std.format.formattedWrite вместо std.string.format и передать параметр
делегат в качестве первого параметра (в UFCS ниже). Также обратите внимание, что
следующие вызовы предоставляют строки формата в качестве аргументов шаблона, чтобы воспользоваться
воспользоваться преимуществами проверки форматных строк в formattedWrite во время компиляции

.. code-block:: d

    import std.stdio;
    import std.format;

    struct Point
    {
        int x;
        int y;
        void toString(void delegate(const(char)[]) sink) const
        {
            sink.formattedWrite!"(%s,%s)"(x, y);
        }
    }

    struct Color
    {
        ubyte r;
        ubyte g;
        ubyte b;
        void toString(void delegate(const(char)[]) sink) const
        {
            sink.formattedWrite!"RGB:%s,%s,%s"(r, g, b);
        }
    }

    struct ColoredPoint
    {
        Color color;
        Point point;
        void toString(void delegate(const(char)[]) sink) const
        {
            sink.formattedWrite!"{%s;%s}"(color, point);
        }
    }

    struct Polygon
    {
        ColoredPoint[] points;
        void toString(void delegate(const(char)[]) sink) const
        {
            sink.formattedWrite!"%s"(points);
        }
    }

    void main()
    {
        auto polygon = Polygon(
            [
            ColoredPoint(Color(10, 10, 10), Point(1, 1)),
            ColoredPoint(Color(20, 20, 20), Point(2, 2)),
            ColoredPoint(Color(30, 30, 30), Point(3, 3))
        ]);
        writeln(polygon);
    }



Преимущество этой программы в том, что, несмотря на то, что в общей сложности 10 вызовов
различных функций toString(), эти вызовы в совокупности дают одну
строку, а не 10.


Резюме
------

* Ключевое слово function предназначено для определения указателей функций, которые будут вызываться позже, как
  как функция.
* Ключевое слово delegate предназначено для определения делегатов. Делегат - это пара из
  указателя функции и контекста, в котором этот указатель функции будет выполняться.
* Делегат может быть создан из объекта и функции-члена с помощью синтаксиса
  синтаксис &object.member_function.
* Делегат можно сконструировать явно, установив его свойства .funcptr и .ptr
  свойства.
* Анонимные функции и анонимные делегаты (lambdas) могут использоваться вместо
  вместо указателей функций и делегатов в выражениях.
* Существует несколько синтаксисов для ламбд, самый короткий из которых предназначен для случаев, когда
  эквивалент состоит только из одного оператора возврата:
  параметр => выражение.
* Более эффективная перегрузка функции toString() использует делегат.
